/**
 *  @file DeflaterInflater.cpp
 */
#include "Deflater.h"
#include "Inflater.h"
#include "../../common/CppMemPool.h"
#include "../../common/UtilFuncT.h"
#include "../../exceptions/RuntimeException.h"
#include "../../text/CFString.h"
#include "zlib.h"

using namespace cppflib::io;
using namespace cppflib::exceptions;
using namespace cppflib::collections;

namespace cppflib
{

namespace util
{

namespace zip
{

#define INBUF_SIZE                 (32 * 1024)
#define GZIP_WINDOW_SIZE           15
#define GZIP_HEADER_PLACEHOLDER    16
#define Z_DEFAULT_MEMLEVEL         8
#define AUTODETECT_HEADER_PLACEHOLDER    32


PRIVATE ptrany_t __ZlibAllocFunc(void* opaque, u32_t items, u32_t size)
{
   return ::MRP_Calloc(items, size);
}

PRIVATE void __ZlibFreeFunc(void* opaque, void* address)
{
   ::MRP_Release(address);
}


/**
 *  Internal class to wrap around zlib functions (deflate and inflate)
 */
class ZlibInfo : public BaseObject
{
private:
   bool isDeflate;        ///< true if this class is instantiated for deflating
   z_streamp pZInfo;      ///< pointer to zlib context
   Stream *pInStream;     ///< input stream
   sz_t inStreamLength;   ///< expected length of input stream
   sz_t curReadLength;    ///< length of stream read so far
   ArrayByte inBuf;       ///< internal buffer for reading from input stream
   int inBufValidLength;  ///< valid length of internal buffer
   bool allDataRead;      ///< mark if all input data is read
   bool allDataProcessed; ///< whether all data is deflated/inflated for the current input stream

private:
   ZlibInfo() : inBuf(INBUF_SIZE)
   {
   }

   void MarkAllDataRead()
   {
      allDataRead = true;
   }

   bool IsAllDataRead()
   {
      return allDataRead;
   }

   void ClearInternalBuffer()
   {
      inBufValidLength = 0;
      //CFString::Memset(inBuf.GetRawPtr(), 0, inBuf.GetSize());
   }

   void ResetState()
   {
      ClearInternalBuffer();
      inStreamLength = 0;
      curReadLength = 0;
      allDataRead = false;
      allDataProcessed = false;
   }

   void ResetZlibInfo()
   {
      if (isDeflate)
         deflateReset(pZInfo);
      else 
         inflateReset(pZInfo);
      ResetState();
   }

   void CloseInputStream()
   {
      if (pInStream) {
         cppmempool::Release(pInStream);
         pInStream = NULL;
      }
   }

   /**
    *  Fill internal buffer (inBuf) and update 'inBufValidLength' at the same time
    *
    *  @return true -- data is filled in inBuf
    *  @return false -- no data is filled (end of input data is reached)
    */
   bool FillInternalBuffer()
   {
      if (inBufValidLength != 0)  // already filled with some data
         return true;

      if (IsAllDataRead())
         return false;

      int nrRead = 0;
      while (nrRead < static_cast<int>(inBuf.GetSize())) {

         int readSize = 0;

         if (inStreamLength > 0) {
            if (curReadLength >= inStreamLength) {
               MarkAllDataRead();
               break;
            }

            // read with known stream length
            readSize = cppflib::Min<int>(static_cast<int>(inStreamLength - curReadLength),
                                         static_cast<int>(inBuf.GetSize() - nrRead));
         }
         else {
            // stream length is unknown
            readSize = static_cast<int>(inBuf.GetSize() - nrRead);
         }

         if (readSize == 0)
            break;

         int tempRead = pInStream->Read(inBuf, nrRead, readSize);
         if (tempRead <= 0) {
            MarkAllDataRead();
            break;
         }

         nrRead += tempRead;
         curReadLength += tempRead;

      } // end of while

      
      inBufValidLength = nrRead;

      // setup input buffer
      pZInfo->next_in = inBuf.GetRawPtr();
      pZInfo->avail_in = inBufValidLength;

      return (inBufValidLength > 0);
   }

   void __Initialize()
   {
      pZInfo->zalloc = &__ZlibAllocFunc;
      pZInfo->zfree = &__ZlibFreeFunc;
      pZInfo->opaque = Z_NULL;
   }

public:
   explicit ZlibInfo(bool isDeflate) : inBuf(INBUF_SIZE)
   {
      pZInfo = static_cast<z_streamp>(::MRP_Calloc(1, sizeof(z_stream)));
      this->isDeflate = isDeflate;
      pInStream = NULL;
      ResetState();
   }

   virtual ~ZlibInfo()
   {
      if (isDeflate)
         deflateEnd(pZInfo);
      else
         inflateEnd(pZInfo);

      ::MRP_Release(pZInfo);

      CloseInputStream();
   }

   /**
    *  For deflater
    */
   int Initialize(Deflater::headertype_t type)
   {
      __Initialize();

      int ret = Z_OK;
      switch (type) {
         case Deflater::NO_HEADER:
            ret = deflateInit2(pZInfo, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -GZIP_WINDOW_SIZE, 
                               Z_DEFAULT_MEMLEVEL, Z_DEFAULT_STRATEGY);  
            break;
         case Deflater::ZLIB:
            ret = deflateInit(pZInfo, Z_DEFAULT_COMPRESSION);
            break;
         case Deflater::GZIP:
            ret = deflateInit2(pZInfo, Z_DEFAULT_COMPRESSION, Z_DEFLATED, GZIP_WINDOW_SIZE + GZIP_HEADER_PLACEHOLDER, 
                               Z_DEFAULT_MEMLEVEL, Z_DEFAULT_STRATEGY);  
            break;
      }

      return ret;
   }

   /**
    *  For inflater
    */
   int Initialize(Inflater::headertype_t type)
   {
      __Initialize();

      int ret = Z_OK;
      switch (type) {
         case Inflater::NO_HEADER:
            ret = inflateInit2(pZInfo, -GZIP_WINDOW_SIZE);  
            break;
         case Inflater::AUTODETECT:
            ret = inflateInit2(pZInfo, GZIP_WINDOW_SIZE + AUTODETECT_HEADER_PLACEHOLDER);
            break;
      }

      return ret;
   }

   /**
    *  Set input stream with an unknown length
    *  
    *  @note internal state is reset to very beginning if a new stream is assigned
    */
   void SetInputStream(Stream *pStream)
   {
      cppmempool::Retain(pStream);
      CloseInputStream();
      this->pInStream = pStream;

      ResetZlibInfo();
   }

   /**
    *  Set input stream with an expected length
    *  
    *  @note internal state is reset to very beginning if a new stream is assigned
    */
   void SetInputStream(Stream *pStream, sz_t streamLength)
   {
      SetInputStream(pStream);
      inStreamLength = streamLength; // replace the original one
   }

   
   /**
    *  @return number of bytes deflated
    *  @return 0 means all data is deflated
    */
   int __Deflate(ArrayByte &buffer, int offset, int count)
   {
      if (allDataProcessed || !pInStream)
         return 0;

      int deflatedBytes = 0;
     
      do {
         int flush = FillInternalBuffer() ? Z_NO_FLUSH : Z_FINISH;
      
         pZInfo->avail_out = count;
         pZInfo->next_out = buffer.GetRawPtr() + offset;
         int ret = deflate(pZInfo, flush);
         if (ret == Z_STREAM_ERROR)  // Z_BUF_ERROR is not fatal
            throw RuntimeException(_S("Deflating error"), ret);

         deflatedBytes = count - static_cast<int>(pZInfo->avail_out);
            
         if (pZInfo->avail_out != 0) 
            ClearInternalBuffer();   // clear internal buffer prepared to be filled in next round
      
         if (ret == Z_STREAM_END && flush == Z_FINISH)
            allDataProcessed = true;

      } while (deflatedBytes == 0 && !allDataProcessed); // sometimes deflatedBytes may be zero and requires more input
      
      return deflatedBytes;
   }

   int Deflate(ArrayByte &buffer, int offset, int count)
   {
      int deflatedBytes = 0;

      try {
         deflatedBytes = __Deflate(buffer, offset, count);
      }
      catch(BaseException &e) {
         ResetZlibInfo();
         CloseInputStream();
         throw e;   // throw again
      }

      return deflatedBytes;
   }


   /**
    *  @return number of bytes inflated
    *  @return 0 means all data is inflated
    */
   int __Inflate(ArrayByte &buffer, int offset, int count)
   {
      if (allDataProcessed || !pInStream)
         return 0;

      int inflatedBytes = 0;
      do {
         if (!FillInternalBuffer()) 
            break;
      
         pZInfo->avail_out = count;
         pZInfo->next_out = buffer.GetRawPtr() + offset;
         int ret = inflate(pZInfo, Z_NO_FLUSH);   
         if (ret == Z_STREAM_ERROR || ret == Z_NEED_DICT || 
             ret == Z_DATA_ERROR || ret == Z_MEM_ERROR)      // Z_BUF_ERROR is not fatal
            throw RuntimeException(_S("Inflating error"), ret);

         inflatedBytes = count - static_cast<int>(pZInfo->avail_out);
            
         if (pZInfo->avail_out != 0) 
            ClearInternalBuffer();   // clear internal buffer prepared to be filled in next round
      
         if (ret == Z_STREAM_END)
            allDataProcessed = true;

      } while(inflatedBytes == 0 && !allDataProcessed);
      
      return inflatedBytes;
   }

   int Inflate(ArrayByte &buffer, int offset, int count)
   {
      int inflatedBytes = 0;

      try {
         inflatedBytes = __Inflate(buffer, offset, count);
      }
      catch(BaseException &e) {
         ResetZlibInfo();
         CloseInputStream();
         throw e;   // throw again
      }

      return inflatedBytes;
   }


};  // end of Deflater


/***************************************** 
 *              Deflater                 *
 *****************************************/


Deflater::Deflater(void)
{
   pZlibInfo = NULL;
}

Deflater::~Deflater(void)
{
   ReleaseBeforeThrowing();
}

Deflater::Deflater(Deflater::headertype_t type)
{
   pZlibInfo = new ZlibInfo(true);
   cppmempool::Retain(pZlibInfo);
   
   int ret = Z_OK;
   if ((ret = pZlibInfo->Initialize(type)) != Z_OK)
      THROW_CTOR_EXCEP_CODE(RuntimeException, _S("Initializing Deflater failed"), ret);
}

void Deflater::ReleaseBeforeThrowing()
{
   if (pZlibInfo) {
      cppmempool::Release(pZlibInfo);
      pZlibInfo = NULL;
   }
}

/**
 *  Initialize input stream
 *  
 *  @note internal state is reset to very beginning if a new stream is assigned
 */
void Deflater::SetInputStream(Stream *pStream)
{
   pZlibInfo->SetInputStream(pStream);
}

/**
 *  Initialize input stream with a possible length of the stream
 *  
 *  @note internal state is reset to very beginning if a new stream is assigned
 */
void Deflater::SetInputStream(io::Stream *pStream, sz_t streamLength)
{
   pZlibInfo->SetInputStream(pStream, streamLength);
}

/**
 *  Call this repeatedly until 0 bytes retured
 */
int Deflater::Deflate(collections::ArrayByte &buffer)
{
   return Deflate(buffer, 0, static_cast<int>(buffer.GetSize()));
}

/**
 *  Call this repeatedly until 0 bytes retured
 */
int Deflater::Deflate(collections::ArrayByte &buffer, int offset, int count)
{
   if (count < 0 || offset < 0 || offset >= static_cast<int>(buffer.GetSize()))
      return 0;

   // use the smaller one
   count = cppflib::Min<int>(count, static_cast<int>(buffer.GetSize() - offset));

   return pZlibInfo->Deflate(buffer, offset, count);
}


/***************************************** 
 *              Inflater                 *
 *****************************************/

Inflater::Inflater(void)
{
   pZlibInfo = NULL;
}

Inflater::~Inflater(void)
{
   ReleaseBeforeThrowing();
}

Inflater::Inflater(Inflater::headertype_t type)
{
   pZlibInfo = new ZlibInfo(false);
   cppmempool::Retain(pZlibInfo);
   
   int ret = Z_OK;
   if ((ret = pZlibInfo->Initialize(type)) != Z_OK)
      THROW_CTOR_EXCEP_CODE(RuntimeException, _S("Initializing Inflater failed"), ret);
}

void Inflater::ReleaseBeforeThrowing()
{
   if (pZlibInfo) {
      cppmempool::Release(pZlibInfo);
      pZlibInfo = NULL;
   }
}

/**
 *  Initialize input stream
 *  
 *  @note internal state is reset to very beginning if a new stream is assigned
 */
void Inflater::SetInputStream(Stream *pStream)
{
   pZlibInfo->SetInputStream(pStream);
}

/**
 *  Initialize input stream with a possible length of the stream
 *  
 *  @note internal state is reset to very beginning if a new stream is assigned
 */
void Inflater::SetInputStream(io::Stream *pStream, sz_t streamLength)
{
   pZlibInfo->SetInputStream(pStream, streamLength);
}

/**
 *  Call this repeatedly until 0 bytes retured
 */
int Inflater::Inflate(collections::ArrayByte &buffer)
{
   return Inflate(buffer, 0, static_cast<int>(buffer.GetSize()));
}

/**
 *  Call this repeatedly until 0 bytes retured
 */
int Inflater::Inflate(collections::ArrayByte &buffer, int offset, int count)
{
   if (count < 0 || offset < 0 || offset >= static_cast<int>(buffer.GetSize()))
      return 0;

   // use the smaller one
   count = cppflib::Min<int>(count, static_cast<int>(buffer.GetSize() - offset));

   return pZlibInfo->Inflate(buffer, offset, count);
}

} // end of namespace zip

} // end of namespace util

} // end of namespace cppflib
